#include "minicon.h"
#include "miniopt.h"

//===========================================================================================================

extern int err_ret(int errcode, const char *msg, const WCHAR *name);

#if defined(_WIN64) || defined(_DEBUG) || !defined(_M_IX86)
  static void __forceinline llmuladd(__int64 &sum,unsigned __int32 mul,unsigned __int32 add)
  { sum = (__int64)((unsigned __int64)sum * mul + add); }
#else
  void __fastcall llmuladd(__int64 &sum,unsigned __int32 mul,unsigned __int32 add);
#endif

static __int64 get_skip( const WCHAR *pw )
{
   unsigned a = 0, mul = 1, len = 0, ul = Opt::wtou(pw, 0, &len);
   if ( len <= 0 ) return -1;
   __int64 ll = ul;


   switch ( pw[len] ) {
      case L'k': mul = 1000; break;
      case L'K': mul = 1024; a = 1024-1000; break;
      case L'm': mul = 1000*1000; break;
      case L'M': mul = 1024*1024; a = 1024*1024-1000*1000; break;
      case L'g': mul = 1000*1000*1000; break;
      case L'G': mul = 1024*1024*1024; a = 1024*1024*1024-1000*1000*1000; break;
   }
   if ( mul > 1 && (L'b' == pw[len+1] || L'B' == pw[len+1]) ) mul -= a;

   llmuladd(ll, mul, 0);
   return ll;
}

static void oct3( unsigned char uc )
{
   char ss[3];
   ss[0] = (char)((uc >> 6) + '0');
   ss[1] = (char)(((uc >> 3) & 7) + '0');
   ss[2] = (char)((uc & 7) + '0');
   Con::o(ss, 3);
}
 
//===========================================================================================================

static int open_file( HANDLE &fh, const WCHAR *fname, __int64 skip, bool &eof )
{
   if ( 0 == ::lstrcmpW(L"-", fname) ) fh = ::GetStdHandle(STD_INPUT_HANDLE); else
   fh = ::CreateFileW(fname, GENERIC_READ, FILE_SHARE_READ, NULL, OPEN_EXISTING, 0, NULL);
   if ( INVALID_HANDLE_VALUE == fh ) return err_ret(-24, "Can't open:", fname);
   if ( skip ) {
      LARGE_INTEGER li;
      li.LowPart = ::GetFileSize(fh, (LPDWORD)&li.HighPart);
      if ( NO_ERROR == ::GetLastError() ) {
         if ( li.QuadPart <= skip ) eof = true; else {
            li.QuadPart = skip;
            ::SetFilePointer(fh, (LONG)li.LowPart, &li.HighPart, FILE_BEGIN);
            if ( NO_ERROR != ::GetLastError() ) return err_ret(-25,"seek error:",fname);
         }
      }
      else { // can't use seek for pipe or other special 'files'
         char bf[2048];
         for(;;) {
            DWORD dw = 0, nr = (DWORD)(skip > sizeof(bf) ? sizeof(bf) : (DWORD)skip);
            if (!::ReadFile(fh, bf, nr, &dw, NULL) && ::GetLastError() != ERROR_BROKEN_PIPE)
            { return err_ret(-26,"read error:",fname); }
            if ( 0 == dw ) { eof = true; break; }
            if ( 0 == (skip -= dw) ) break;
         }
      }
   }
   return 0;
}

//===========================================================================================================

static int read_file( HANDLE fh, const WCHAR *fname, void *bf, size_t nb, bool &eof, size_t &nr)
{
   DWORD dw = 0;
   if (!::ReadFile(fh, bf, (DWORD)nb, &dw, NULL) && ::GetLastError() != ERROR_BROKEN_PIPE)
   { return err_ret(-26,"read error:",fname); }
   eof = (dw == 0);
   nr = dw;
   return 0;
}

//===========================================================================================================

static void prints(int verbose, bool print, __int64 pos, unsigned char c1,unsigned char c2)
{
   if ( 0 == verbose ) {
      if (!print) return;
      Con::o(" is ");
   }
   else {
      Con::o(pos); Con::o(' ');
   }

   oct3(c1);
   if ( print ) {
      Con::o(' ');
      if ( c1 >= 0x20 ) { Con::o((char)c1); Con::o(' '); }
      else     { Con::o('^'); Con::o((char)(c1 + 0x40)); }
   }

   Con::o(' '); if ( verbose > 0 && print ) Con::o("  ");

   oct3(c2);
   if ( print ) {
      Con::o(' ');
      if ( c2 >= 0x20 ) Con::o((char)c2);
      else     { Con::o('^'); Con::o((char)(c2 + 0x40)); }
   }
}

//===========================================================================================================

int 
cmp( WCHAR **args,int na, const WCHAR *skip,const WCHAR *limit, int verbose, bool print )
{
   const WCHAR *file1=NULL, *file2=NULL, *skip1=NULL, *skip2=NULL;
   int rc, iLimit = 0, diff = 0, line = 1;
   __int64 llSkip1 = 0, llSkip2 = 0, pos = 0;
   bool eof1 = false, eof2 = false, done = false;
   unsigned char *bf1 = NULL, *bf2 = NULL; size_t n, n1 = 0, n2 = 0, bsz = 16*1024; 

   if ( na < 1 ) return Opt::opt_err(Opt::Err_Missed_Param);
   file1 = args[0];
   if ( na > 4 || (skip && na > 2) ) return Opt::opt_err(Opt::Err_TooMany_Params);
   if ( na >= 2 ) file2 = args[1];
   if ( na >= 3 ) skip1 = args[2]; else skip1 = skip;
   if ( na >= 4 ) skip2 = args[3]; else if ( skip1 ) {
      skip2 = skip1; while ( *skip2 && L':' != *skip2 ) ++skip2; skip2 = *skip2 ? skip2+1 : skip1;
   }

   ::SetLastError(0);
   if ( limit ) iLimit = Opt::wtoi(limit);
   if ( iLimit < 0 ) return err_ret(-21, "Bad Limit value:", limit);
   if ( skip1 ) llSkip1 = get_skip(skip1);
   if ( llSkip1 < 0 ) return err_ret(-22, "Bad Skip1 value:", skip1);
   if ( skip2 ) llSkip2 = get_skip(skip2);
   if ( llSkip2 < 0 ) return err_ret(-23, "Bad Skip2 value:", skip2);

   if (!file1) file1 = L"-";
   if (!file2) file2 = L"-";
   HANDLE fh1 = INVALID_HANDLE_VALUE, fh2 = INVALID_HANDLE_VALUE;
   rc = open_file(fh1, file1, llSkip1, eof1); if (0 != rc) goto e;
   rc = open_file(fh2, file2, llSkip2, eof2); if (0 != rc) goto e;

   bf1 = bf2 = NULL; Win::alloc((void* &)bf1, (int)bsz); Win::alloc((void* &)bf2, (int)bsz);

   for (;;) {
      n = n1 <= n2 ? n1 : n2;
      if ( 0 == n ) {
         if ( eof1 && eof2 ) break; // equal files
         if ( iLimit > 0 && bsz > (size_t)iLimit ) bsz = iLimit;
         if ( !n1 && !eof1 ) if ( 0 != (rc = read_file(fh1, file1, bf1, bsz, eof1, n1)) ) break;
         if ( !n2 && !eof2 ) if ( 0 != (rc = read_file(fh2, file2, bf2, bsz, eof2, n2)) ) break;
         n = n1 <= n2 ? n1 : n2;
         if ( 0 == n ) {
            if ( verbose == 0 && (n1 && n2) ) Con::f(L"%s %s differ: ", file1, file2);
            if ( n1 > 0 ) {
               if ( verbose >= 0 ) Con::f(L"%s is longer\r\n", file1);
               if (!diff) diff = 2;
            }
            else if ( n2 > 0 ) {
               if ( verbose >= 0 ) Con::f(L"%s is longer\r\n", file2);
               if (!diff) diff = 3;
            }
            break;
         }
      }

      for (size_t i = 0; i < n; ++i) {
         ++pos;
         if ( bf1[i] == bf2[i] ) { if ( bf1[i] == '\n' ) ++line; }
         else {
            if ( !diff ) {
               diff = 1;
               if ( verbose == 0 ) {
                  Con::f(L"%s %s differ: byte ", file1, file2); Con::o(pos); Con::f(", line %d", line);
                  prints(verbose, print, pos, bf1[i], bf2[i]);
                  Con::n();
               }
               if ( verbose <= 0 ) break;
            }
            prints(verbose, print, pos, bf1[i], bf2[i]);
            Con::n();
         }
         if (iLimit && --iLimit == 0) { done = true; break; }
      }
      if ( (diff && verbose <= 0) || done ) break;

      if ((n1 -= n) > 0) Win::bmov(bf1, bf1+n, n1);
      if ((n2 -= n) > 0) Win::bmov(bf2, bf2+n, n1);
   }

e: if ( INVALID_HANDLE_VALUE != fh2 ) ::CloseHandle(fh2);
   if ( INVALID_HANDLE_VALUE != fh1 ) ::CloseHandle(fh1);
   return rc ? rc : -diff;
}

//===========================================================================================================
//===========================================================================================================
